#include "DateTime.h"
#include <algorithm>
#include <sstream>
#include <iomanip>
namespace flyboy{
	static const size_t __monthTable[13] = {
		-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};

	const std::string __weekTable[] = {
		"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday",
	};

	static inline bool isLeapYear(int year){
		if (year % 400 == 0){
			return true;
		}
		if (year % 100 == 0){
			return false;
		}
		if (year % 4 == 0){
			return true;
		}
		return false;
	}

#	define assert_year(year)	\
		assert(static_cast<int>(year) >= 0);

#	define assert_month(month)	\
		assert(month > 0 && month < 13)
#	define assert_day(__day, __month, __leap)	do{\
		size_t day__ = __day;	\
		size_t month__ = __month;	\
		bool leap__ = __leap;		\
		assert(day__ > 0 && \
		day__ <= getMonthDays(month__) + (leap__ && month__ == 2));	\
	}while(0)

#	define assert_week(week)	\
		assert(week >= DateTime::WeekType::WEEK_MIN &&\
			week < DateTime::WeekType::WEEK_MAX)

#	define assert_diff(diff)	\
		assert(diff >= DateTime::DateDiffType::DIFF_MIN &&\
			diff < DateTime::DateDiffType::DIFF_MAX)

	static inline size_t nextMonth(size_t month){
		assert(month > 0 && month < 13);
		month++;
		if (month >= 13){
			month = 1;
		}
		return month;
	}

	static inline size_t getMonthDays(size_t month, bool leap = false){
		assert_month(month);
		return __monthTable[month] + (month == 2 && leap);
	}

	static inline size_t getPassedDaysInYear(size_t month, size_t day, bool leap){
		size_t ret = 0;
		assert_month(month);
		assert_day(day, month, leap);
		for (size_t i = 1; i < month; i++){
			ret += getMonthDays(i, leap);
		}
		ret += day;
		return ret;
	}

	static inline size_t getPassedDaysInMonth(size_t month, size_t day, bool leap){
		return day;
	}

	static inline void fixWeek(DateTime::WeekType& week, int delta){
		assert_week(week);
		size_t weekVar = static_cast<size_t>(week);
		if (delta > 0){
			//adjust week
			weekVar += delta;
			weekVar %= static_cast<size_t>(DateTime::WeekType::WEEK_MAX);
		}
		else{
			//adjust week
			delta = -delta;
			delta = delta % static_cast<size_t>(DateTime::WeekType::WEEK_MAX);
			weekVar = (weekVar + static_cast<size_t>(DateTime::WeekType::WEEK_MAX)) - delta;
			weekVar %= static_cast<size_t>(DateTime::WeekType::WEEK_MAX);
		}
		week = static_cast<DateTime::WeekType>(weekVar);
	}

	void adjustDayForward(size_t& year, size_t& month, size_t& day, int delta){
		bool currentYearLeap = isLeapYear(year);
		size_t daysInCurrentYear = getPassedDaysInYear(month, day, currentYearLeap);
		size_t leftDaysInCurrentYear = (currentYearLeap ? 366 : 365) - daysInCurrentYear;
		if (delta >= static_cast<int>(leftDaysInCurrentYear + 1)){
			//travel to next year 1 month 1 day
			delta -= static_cast<int>(leftDaysInCurrentYear + 1);
			year++;
			month = 1;
			day = 1;
		}
		daysInCurrentYear = isLeapYear(year) ? 366 : 365;
		while (delta >= static_cast<int>(daysInCurrentYear)){
			year++;
			delta -= static_cast<int>(daysInCurrentYear);
			daysInCurrentYear = isLeapYear(year) ? 366 : 365;
		}
		if (delta){
			//change days, but it also in current year
			currentYearLeap = isLeapYear(year);
			size_t leftDaysInCurrentMonth = getMonthDays(month, currentYearLeap) -
				getPassedDaysInMonth(month, day, currentYearLeap);
			if (delta >= static_cast<int>(leftDaysInCurrentMonth + 1)){
				//travel to next month 1 day
				delta -= static_cast<int>(leftDaysInCurrentMonth + 1);
				month++;
				day = 1;
			}
			size_t daysInCurrentMonth = getMonthDays(month, currentYearLeap);
			while (delta >= static_cast<int>(daysInCurrentMonth)){
				delta -= static_cast<int>(daysInCurrentMonth);
				month++;
				daysInCurrentMonth = getMonthDays(month, currentYearLeap);
			}

			if (delta){
				day += delta;
			}
		}
	}

	void adjustDayBackward(size_t& year, size_t& month, size_t& day, int delta){
		bool currentYearLeap = isLeapYear(year);
		size_t daysInCurrentYear = getPassedDaysInYear(month, day, currentYearLeap);

		if (delta >= static_cast<int>(daysInCurrentYear)){
			//travel to prev year 12 month 31 day
			delta -= static_cast<int>(daysInCurrentYear);
			year--;
			assert_year(year);
			month = 12;
			day = 31;
		}
		daysInCurrentYear = isLeapYear(year) ? 366 : 365;
		while (delta >= static_cast<int>(daysInCurrentYear)){
			year--;
			assert_year(year);
			delta -= static_cast<int>(daysInCurrentYear);
			daysInCurrentYear = isLeapYear(year) ? 366 : 365;
		}
		if (delta){
			//change days, but it also in current year
			currentYearLeap = isLeapYear(year);
			size_t currentDaysInMonth = getPassedDaysInMonth(month, day, currentYearLeap);
			if (delta >= static_cast<int>(currentDaysInMonth)){
				//travel to next month last day
				delta -= static_cast<int>(currentDaysInMonth);
				month--;
				day = getMonthDays(month, currentYearLeap);
			}
			size_t daysInCurrentMonth = getMonthDays(month, currentYearLeap);
			while (delta >= static_cast<int>(daysInCurrentMonth)){
				delta -= static_cast<int>(daysInCurrentMonth);
				month--;
				daysInCurrentMonth = getMonthDays(month, currentYearLeap);
				day = daysInCurrentMonth;
			}

			if (delta){
				day -= delta;
			}
		}
	}
	void adjustDay(const DateTime& time, int delta, DateTime& ret){
		if (delta == 0){
			ret = time;
			return;
		}
		size_t retYear, retMonth, retDay;
		DateTime::WeekType retWeek;
		retYear = time.GetYear();
		retMonth = time.GetMonth();
		retDay = time.GetDay();
		retWeek = time.GetWeek();
		if (delta > 0){
			adjustDayForward(retYear, retMonth, retDay, delta);
		}
		else{
			adjustDayBackward(retYear, retMonth, retDay, -delta);
		}
		//adjust week
		fixWeek(retWeek, delta);
		ret = DateTime(retYear, retMonth, retDay, retWeek);
	}

	void adjustMonthForward(size_t& year, size_t& month, int delta){
		size_t leftMonthInYear = 12 - month;
		if (delta >= static_cast<int>(leftMonthInYear + 1)){
			//change month to next year 1 month
			delta -= static_cast<int>(leftMonthInYear + 1);
			month = 1;
			year++;
		}

		while (delta >= 12){
			delta -= 12;
			year++;
		}
		month += delta;
	}

	void adjustMonthBackward(size_t& year, size_t& month, int delta){
		if (delta >= static_cast<int>(month)){
			//change to prev year last month
			delta -= static_cast<int>(month);
			month = 12;
			year--;
			assert_year(year);
		}
		while (delta >= 12){
			delta -= 12;
			year--;
			assert_year(year);
		}
		month -= delta;
	}
	void adjustMonth(const DateTime& time, int delta, DateTime& ret){
		if (delta == 0){
			ret = time;
			return;
		}
		size_t retYear, retMonth, retDay;
		DateTime::WeekType retWeek;
		retYear = time.GetYear();
		retMonth = time.GetMonth();
		retDay = time.GetDay();
		retWeek = time.GetWeek();
		if (delta > 0){
			adjustMonthForward(retYear, retMonth, delta);
		}
		else{
			adjustMonthBackward(retYear, retMonth, -delta);
		}
		ret = DateTime(retYear, retMonth, retDay);
		delta = std::get<static_cast<int>(DateTime::DateDiffType::DIFF_DAY)>(ret - time);
		fixWeek(retWeek, delta);
		ret = DateTime(retYear, retMonth, retDay, retWeek);
	}

	void adjustYear(const DateTime& time, int delta, DateTime& ret){
		if (delta == 0){
			ret = time;
			return;
		}
		size_t retYear, retMonth, retDay;
		DateTime::WeekType retWeek;
		retYear = time.GetYear();
		retMonth = time.GetMonth();
		retDay = time.GetDay();
		retWeek = time.GetWeek();
		if (delta > 0){
			retYear += delta;
		}
		else{
			assert_year(static_cast<int>(time.GetYear()) - delta);
			retYear -= delta;
		}
		ret = DateTime(retYear, retMonth, retDay);
		//adjust week
		delta = std::get<static_cast<int>(DateTime::DateDiffType::DIFF_DAY)>(ret - time);
		fixWeek(retWeek, delta);
		ret = DateTime(retYear, retMonth, retDay, retWeek);
	}

	void(*adjustWhich[])(const DateTime&, int, DateTime&) = {
		adjustYear,
		adjustMonth,
		adjustDay,
		nullptr,
	};


	DateTime::DateTime(int year, int month, int day, DateTime::WeekType week){
		assert_year(year);
		assert_month(month);
		assert_day(day, month, isLeapYear(year));
		assert_week(week);
		this->year = static_cast<size_t>(year);
		this->month = static_cast<size_t>(month);
		this->day = static_cast<size_t>(day);
		this->week = week;
	}
	DateTime::~DateTime(){

	}

	void DateTime::Adjust(DateTime::AdjustType which, int delta){
		assert(which >= AdjustType::ADJUST_MIN && which < AdjustType::ADJUST_MAX);
		assert(adjustWhich[static_cast<int>(which)] != nullptr);
		DateTime tmpTime;
		adjustWhich[static_cast<int>(which)](*this, delta, tmpTime);
		*this = tmpTime;
	}

	const std::tuple<int, int, int> operator -(const DateTime& one, const DateTime& another){
		int diffYears = 0, diffMonths = 0, diffDays = 0;
		diffYears = one.GetYear();
		diffYears -= another.GetYear();

		int onePassedDays = getPassedDaysInYear(one.GetMonth(), one.GetDay(), isLeapYear(one.GetYear()));
		int anotherPassedDays = getPassedDaysInYear(another.GetMonth(), another.GetDay(), isLeapYear(another.GetYear()));
		int oneLeftDays = (isLeapYear(one.GetYear()) ? 366 : 365) - onePassedDays;
		int anotherLeftDays = (isLeapYear(another.GetYear()) ? 366 : 365) - anotherPassedDays;

		int minYear = std::min(one.GetYear(), another.GetYear());
		int maxYear = std::max(one.GetYear(), another.GetYear());

		for (int year = minYear; year < maxYear; year++){
			diffDays += isLeapYear(year) ? 366 : 365;
			diffMonths += 12;
		}
		if (one.GetYear() >= another.GetYear()){
			diffDays += onePassedDays - anotherPassedDays;
			diffMonths += one.GetMonth();
			diffMonths -= another.GetMonth();
		}
		else{
			diffDays += anotherPassedDays - onePassedDays;
			diffMonths += another.GetMonth();
			diffMonths -= one.GetMonth();
			diffDays = -diffDays;
			diffMonths = -diffMonths;
		}
		return std::tuple < int, int, int > { diffYears, diffMonths, diffDays };
	}

	const DateTime operator-(const DateTime& one, int delta){
		DateTime ret;
		adjustDay(one, -delta, ret);
		return ret;
	}

	const DateTime operator-(int delta, const DateTime& one){
		DateTime ret;
		adjustDay(one, -delta, ret);
		return ret;
	}

	const DateTime operator+(const DateTime& one, int delta){
		DateTime ret;
		adjustDay(one, delta, ret);
		return ret;
	}

	const DateTime operator+(int delta, const DateTime& one){
		DateTime ret;
		adjustDay(one, delta, ret);
		return ret;
	}

	std::ostream& operator<<(std::ostream& stream, const DateTime& time){
		stream << time.operator std::string();
		return stream;
	}

	DateTime::operator std::string() const{
		std::stringstream strstream;
		strstream << std::setw(4) << std::setfill('0') << year << "-"
			<< std::setw(2) << month << "-"
			<< std::setw(2)<< day
			<<" " <<__weekTable[static_cast<size_t>(week)];
		return strstream.str();
	}

	const std::string FormatTuple(const std::tuple<int, int, int>& value){
		std::stringstream strstream;
		strstream << "differ Years:<" << std::get<
				static_cast<size_t>(DateTime::DateDiffType::DIFF_YEAR)
			>(value) << ">\t" <<
			"differ Months:<" << std::get<
				static_cast<size_t>(DateTime::DateDiffType::DIFF_MONTH)
			>(value) << ">\t" <<
			"differ Days:<" << std::get<
				static_cast<size_t>(DateTime::DateDiffType::DIFF_DAY)
			>(value) << ">";
		return strstream.str();
	}

	/*size_t GetDiffVar(const std::tuple<int, int, int>& value, 
		const DateTime::DateDiffType& type){
		assert_diff(type);
		return std::get<static_cast<size_t>(type)>(value);
	}*/
}